package person.bangbang.im.Androidgin.Fetion;

import java.util.ArrayList;
import java.util.List;

import person.bangbang.im.Androidgin.Framework.Account;
import person.bangbang.im.Androidgin.Framework.Accounts;
import person.bangbang.im.Androidgin.Framework.Buddies;
import person.bangbang.im.Androidgin.Framework.Buddy;
import person.bangbang.im.Androidgin.Framework.Conversation;
import person.bangbang.im.Androidgin.Framework.Conversations;
import person.bangbang.im.Androidgin.Framework.Message;
import person.bangbang.im.Androidgin.Framework.Protocol;
import person.bangbang.im.Androidgin.Framework.StatusType;
import person.bangbang.im.Androidgin.Util.Log;
import android.os.Bundle;
import android.os.Handler;



public class FetionProtocol extends Protocol {
	private static final String TAG = "FetionProtocol";
	
	private static int STEP_COUNT = 9; // total login step count
	
	public static String PROTOCOL_ID = "fetion";
	
	private static int MAJOR = 0;
	private static int MINOR = 1;
	private static int PATCH = 0;
	
	private SsiAppSignin mSsiApp;
	private TcpSendAndReceive mTcpConn;
	private SerialManager mSerialManager;
	
	private Account mAcc;
	
	private FetionUri mUri;
	
	// FIXME a temp solution for sned Im
	// beeause we must get frieng's uri (oh actually this is a String,sadlly)
	// from user's name. 
	// is there a clean solution ???
	
	private ArrayList<FetionFriend> mBuddies;
	

	
	private Buddies mBuddiees = Buddies.getInstance();
	private Conversations mChats = Conversations.getInstance();
	
	private FetionFriend me = new FetionFriend();
	
    private IStatusChangedReceiver mStatusChangedReceiver = new IStatusChangedReceiver() {
        public boolean FetionSetErrorText(String text) {
        	dispatchLoginErr(-1, text);
            return true;
        }
        public boolean FetionStatusChanged(int oldStatus, int newStatus) {
//            currentStatus = newStatus;
        	Log.d(TAG, "status chanded from:" + oldStatus);
        	Log.d(TAG, "status changed to:" + newStatus);
            FrameWork.synchronizednotify(this);
            return true;
        }

    };
    
    /* shit the capital */
    private TcpSendAndReceive.IConnectionClosedHandler mConnClose = new TcpSendAndReceive.IConnectionClosedHandler(){
    	public boolean ConnectionClosed(TcpSendAndReceive sender) {
    		return true;
    	}
    };
	
	public FetionProtocol(String userName, String passward, StatusType type) {
		super(userName, passward, type);
	}

	public void loginThread(String mUserName, String mPassward) {
		// TODO Auto-generated method stub
		// TODO do it in a thread.
//		Thread loginThread = new LoginThread(this, mUserName, mPassward);
//		loginThread.start();
		android.os.Message msg;
		mAcc = Accounts.getInstance().findAccount(getProtocolID(), mUserName);
			
		Log.d(TAG, "do the login in fetion protocol");
		if ("" == mPassward || null == mPassward) {
//				dispatchLoginErr(0, "no passward. oh U forget it");
				msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "no passward. oh U forget it";
				
				mMainHandler.sendMessage(msg);
			return;
		}
		Log.d(TAG, "login as:" + mUserName);
		Log.d(TAG, "password:" + mPassward);
		
		// STEP 1: SystemConfig.
		SystemConfig sysConfig = new SystemConfig(mUserName);
		sysConfig.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(1, STEP_COUNT, "get system conf");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 1;
		msg.arg2 = STEP_COUNT;
		msg.obj = "get system conf";
		
		mMainHandler.sendMessage(msg);
		try {
			if (false == sysConfig.execute()) {
//				dispatchLoginErr(0, "Error in SysConfig.execute");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "Error in SysConfig.execute";
				
				mMainHandler.sendMessage(msg);
				return;
			}
		} finally {
			sysConfig.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		
		HashedPasswordParameter hashPassward = FetionBase.HashPassword(mPassward);
		Log.d(TAG,"HashdPasswardParameter = " + hashPassward.toString());
		
		// STEP 2: SsiAppSignin
		mSsiApp = new SsiAppSignin(sysConfig.getSsiAppSigninUrl(),
				                               sysConfig.getMobileno(),
				                               hashPassward.passwordDigest);


		mSsiApp.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(2, STEP_COUNT, "signin.. what's meaning");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 2;
		msg.arg2 = STEP_COUNT;
		msg.obj = "signin.. what's meaning";
		
		mMainHandler.sendMessage(msg);
		try {
			if (false == mSsiApp.execute()) {
//				d"Error in SysConfig.execute"ispatchLoginErr(0, "Error in SsiAppSigin.execute");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "Error in SsiAppSigin.execute";
				
				mMainHandler.sendMessage(msg);
				return;
			}
		} finally {
			mSsiApp.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		mUri  = mSsiApp.getUri();
		

		// STEP 3: TcpSendAndrReceive
		mTcpConn = TcpSendAndReceive.getInstance(sysConfig.getSipcproxy_address(),
				                                          sysConfig.getSipcproxy_port());
		mTcpConn.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(3, STEP_COUNT, "tcp connecting");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 3;
		msg.arg2 = STEP_COUNT;
		msg.obj = "tcp connecting";
		
		mMainHandler.sendMessage(msg);
		try {
			if (false == mTcpConn.execute()) {
//				dispatchLoginErr(0, "Error in TcpSendAndReceive.exectue");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "Error in TcpSendAndReceive.exectue";
				
				mMainHandler.sendMessage(msg);				
				return;
			}
		} finally {
			mTcpConn.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		mSerialManager = new SerialManager();
		int status = Settings.STATUS_ONLINE;
		
		
		// STEP 4: SIPCLogin
		SIPCLogin login = new SIPCLogin(mSsiApp.getUri(),
				                        mTcpConn,
				                        mSerialManager,
				                        hashPassward,
				                        mSsiApp.getSsicCookie(),
				                        status);
		login.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(4, STEP_COUNT, "login..");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 4;
		msg.arg2 = STEP_COUNT;
		msg.obj = "login..";
		
		mMainHandler.sendMessage(msg);
		try {
			if (false == login.execute()) {
//				dispatchLoginErr(0, "Error in SIPCLogin.execute");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "Error in SIPCLogin.execute";
				
				mMainHandler.sendMessage(msg);
				return;
			}
		} finally {
			login.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		// XXX is login on at this point ??? 
		
		// TODO delay this 
		// STEP 5 REmotePackageHandler
		RemotePackageHandler remotePackageHandler = new RemotePackageHandler(this,
																			 mSsiApp.getUri(),
																			 mTcpConn,
																			 mSerialManager);
		mTcpConn.addPackageReceivedQueue(remotePackageHandler);
		
		mTcpConn.addConnectionClosedNotifier(mConnClose);
		
//		dispatchLoginStep(5, STEP_COUNT, "get buddy's list");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 5;
		msg.arg2 = STEP_COUNT;
		msg.obj = "get buddy's list";
		
		mMainHandler.sendMessage(msg);
		
		// STEP 6 GetFriendList
		GetFriendsList friendList = new GetFriendsList(mSsiApp.getUri(), mTcpConn, mSerialManager);
		friendList.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(6, STEP_COUNT, "get buddies list");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 6;
		msg.arg2 = STEP_COUNT;
		msg.obj = "get buddies list";
		
		mMainHandler.sendMessage(msg);
		try {
			if (!friendList.execute()) {
//				dispatchLoginErr(0, "Error in GetFreiendsList.execute");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "Error in GetFreiendsList.execute";
				
				mMainHandler.sendMessage(msg);
				return;
			}
		} finally {
			friendList.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		// STEP 7: me
		// more  @see AnFetion
		me.isMe = true;
		me.Uri = mSsiApp.getUri().toString();
        me.mobile_no = mSsiApp.getMobileno();
        me.IsMobileNo = true;
        me.setFriendName("我自己");
//        me.setOnlineStatus(oldstatus == Settings.STATUS_OFFLINE ? Settings.loginStatus : oldstatus);
       
        me.setOnlineStatus(Settings.STATUS_HIDDEN); // TOOD hard-coding
        
        me.type = Settings.TYPE_FRIEND;
        
		ArrayList<FetionFriend> buddies = friendList.getBuddies();
		
		// STEP 8: ReserveNotify
		ReserveNotify reserveNotify = new ReserveNotify(mSsiApp.getUri(), buddies,
                mTcpConn, mSerialManager);
        reserveNotify.addStatusChangedReceiver(mStatusChangedReceiver);
//		dispatchLoginStep(7, STEP_COUNT,"ReserveNotify (what's this)");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 7;
		msg.arg2 = STEP_COUNT;
		msg.obj = "ReserveNotify (what's this)";
		
		mMainHandler.sendMessage(msg);
		
        try {
            if (false ==reserveNotify.execute()) {
//                dispatchLoginErr(0, "An error acurred in ReserveNotify.extcute()");
				  msg = android.os.Message.obtain();
				msg.what = MainHandler.CODE_LOGIN_ERR;
				msg.arg1 = 0;
				msg.obj = "An error acurred in ReserveNotify.extcute()";
				
				mMainHandler.sendMessage(msg);
                return;
            }
        } finally {
            reserveNotify.removeStatusChangedReceiver(mStatusChangedReceiver);
        }

        // STEP 9: GetPersonalInfo
//		dispatchLoginStep(8, STEP_COUNT, "GetPersonalInfo ...");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 8;
		msg.arg2 = STEP_COUNT;
		msg.obj = "GetPersonalInfo ...";
		
		mMainHandler.sendMessage(msg);
        if (!getPersonalInfo(me)) {
            Log.e(TAG, "Error when parse personal info!");
        }
        
		buddies.add(0, me);

		mBuddies = buddies;

		for (FetionFriend b : buddies) {
			mBuddiees.add(new FriendAdapter(b));
		}
	
//		dispatchLoginStep(9, STEP_COUNT,"All is ok");
		  msg = android.os.Message.obtain();
		msg.what = MainHandler.CODE_LOGIN_STEP;
		msg.arg1 = 9;
		msg.arg2 = STEP_COUNT;
		msg.obj = "All is ok";
		
		mMainHandler.sendMessage(msg);

		
//		int loginStepCount = 5;
//		try {
//			Thread.sleep(3*1000);
//
//			for (int i = 0; i < loginStepCount; i++) {
//				
//				mLoginNotify.dispatchLoginStep("do the " + i + " step out of " + loginStepCount + " steps",i, loginStepCount);
//			}
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		

	}
	public void login(String mUserName, String mPassward) {
		// TODO Auto-generated method stub
		// TODO do it in a thread.
//		Thread loginThread = new LoginThread(this, mUserName, mPassward);
//		loginThread.start();
		
		mAcc = Accounts.getInstance().findAccount(getProtocolID(), mUserName);
			
		Log.d(TAG, "do the login in fetion protocol");
		if ("" == mPassward || null == mPassward) {
				dispatchLoginErr(0, "no passward. oh U forget it");
			return;
		}
		Log.d(TAG, "login as:" + mUserName);
		Log.d(TAG, "password:" + mPassward);
		
		// STEP 1: SystemConfig.
		SystemConfig sysConfig = new SystemConfig(mUserName);
		sysConfig.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(1, STEP_COUNT, "get system conf");
		try {
			if (false == sysConfig.execute()) {
				dispatchLoginErr(0, "Error in SysConfig.execute");
				return;
			}
		} finally {
			sysConfig.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		
		HashedPasswordParameter hashPassward = FetionBase.HashPassword(mPassward);
		Log.d(TAG,"HashdPasswardParameter = " + hashPassward.toString());
		
		// STEP 2: SsiAppSignin
		mSsiApp = new SsiAppSignin(sysConfig.getSsiAppSigninUrl(),
				                               sysConfig.getMobileno(),
				                               hashPassward.passwordDigest);


		mSsiApp.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(2, STEP_COUNT, "signin.. what's meaning");
		try {
			if (false == mSsiApp.execute()) {
				dispatchLoginErr(0, "Error in SsiAppSigin.execute");
				return;
			}
		} finally {
			mSsiApp.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		mUri  = mSsiApp.getUri();
		

		// STEP 3: TcpSendAndrReceive
		mTcpConn = TcpSendAndReceive.getInstance(sysConfig.getSipcproxy_address(),
				                                          sysConfig.getSipcproxy_port());
		mTcpConn.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(3, STEP_COUNT, "tcp connecting");
		try {
			if (false == mTcpConn.execute()) {
				dispatchLoginErr(0, "Error in TcpSendAndReceive.exectue");
				return;
			}
		} finally {
			mTcpConn.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		mSerialManager = new SerialManager();
		int status = Settings.STATUS_ONLINE;
		
		
		// STEP 4: SIPCLogin
		SIPCLogin login = new SIPCLogin(mSsiApp.getUri(),
				                        mTcpConn,
				                        mSerialManager,
				                        hashPassward,
				                        mSsiApp.getSsicCookie(),
				                        status);
		login.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(4, STEP_COUNT, "login..");
		try {
			if (false == login.execute()) {
				dispatchLoginErr(0, "Error in SIPCLogin.execute");
				return;
			}
		} finally {
			login.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		// XXX is login on at this point ??? 
		
		// TODO delay this 
		// STEP 5 REmotePackageHandler
		RemotePackageHandler remotePackageHandler = new RemotePackageHandler(this,
																			 mSsiApp.getUri(),
																			 mTcpConn,
																			 mSerialManager);
		mTcpConn.addPackageReceivedQueue(remotePackageHandler);
		
		mTcpConn.addConnectionClosedNotifier(mConnClose);
		
		dispatchLoginStep(5, STEP_COUNT, "get buddy's list");
		
		// STEP 6 GetFriendList
		GetFriendsList friendList = new GetFriendsList(mSsiApp.getUri(), mTcpConn, mSerialManager);
		friendList.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(6, STEP_COUNT, "get buddies list");
		try {
			if (!friendList.execute()) {
				dispatchLoginErr(0, "Error in GetFreiendsList.execute");
				return;
			}
		} finally {
			friendList.removeStatusChangedReceiver(mStatusChangedReceiver);
		}
		
		// STEP 7: me
		// more  @see AnFetion
		me.isMe = true;
		me.Uri = mSsiApp.getUri().toString();
        me.mobile_no = mSsiApp.getMobileno();
        me.IsMobileNo = true;
        me.setFriendName("我自己");
//        me.setOnlineStatus(oldstatus == Settings.STATUS_OFFLINE ? Settings.loginStatus : oldstatus);
       
        me.setOnlineStatus(Settings.STATUS_HIDDEN); // TOOD hard-coding
        
        me.type = Settings.TYPE_FRIEND;
        
		ArrayList<FetionFriend> buddies = friendList.getBuddies();
		
		// STEP 8: ReserveNotify
		ReserveNotify reserveNotify = new ReserveNotify(mSsiApp.getUri(), buddies,
                mTcpConn, mSerialManager);
        reserveNotify.addStatusChangedReceiver(mStatusChangedReceiver);
		dispatchLoginStep(7, STEP_COUNT,"ReserveNotify (what's this)");
        try {
            if (false ==reserveNotify.execute()) {
                dispatchLoginErr(0, "An error acurred in ReserveNotify.extcute()");
                return;
            }
        } finally {
            reserveNotify.removeStatusChangedReceiver(mStatusChangedReceiver);
        }

        // STEP 9: GetPersonalInfo
		dispatchLoginStep(8, STEP_COUNT, "GetPersonalInfo ...");
        if (!getPersonalInfo(me)) {
            Log.e(TAG, "Error when parse personal info!");
        }
        
		buddies.add(0, me);

		mBuddies = buddies;

		for (FetionFriend b : buddies) {
			mBuddiees.add(new FriendAdapter(b));
		}
	
		dispatchLoginStep(9, STEP_COUNT,"All is ok");

		
//		int loginStepCount = 5;
//		try {
//			Thread.sleep(3*1000);
//
//			for (int i = 0; i < loginStepCount; i++) {
//				
//				mLoginNotify.dispatchLoginStep("do the " + i + " step out of " + loginStepCount + " steps",i, loginStepCount);
//			}
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		

	}
	
	
	public Buddy getMyself() {
		// TODO Auto-generated method stub
		return new FriendAdapter(me);
	}

	public List<StatusType> getStatusType() {
		// TODO Auto-generated method stub
		List<StatusType> list = new ArrayList<StatusType>();
		
		list.add(new StatusType(10, "online"));
		list.add(new StatusType(11, "hidemyself"));
		list.add(new StatusType(20, "offline"));
		
		return list;
	}
	
	/**
	 * FIXME a temp solution
	 * a wrapper class for FetionFriend just for inter-working with Androidgin
	 * @author bangbang.song@gmail.com
	 *
	 */
	public class FriendAdapter extends Buddy {
		
		public FriendAdapter(FetionFriend f) {
			super(f.Uri, f.getName(), mAcc);
		}
		public Account getAccount() {
			return super.getAccount();
		}
		public String getUserName() {
			return super.getUserName();
		}
		public String getId() {
			return super.getID();
		}
		public void setUserName(String userName) {
			super.setUserName(userName);
		}
		public void setAlias(String alias) {
			super.setAlias(alias);
		}
		public void setAccount(Account acc) {
			super.setAccount(acc);
		}

	}
	// see AnFetion.FetionService
    public boolean getPersonalInfo(FetionFriend ff) {
        GetPersonalInfo gpi = new GetPersonalInfo(mSerialManager, mTcpConn, mSsiApp.getUri(), ff);
        return gpi.execute();
    }
 	@Override
	public String getProtocolID() {
		// TODO Auto-generated method stub
		return "fetion";
	}
	@Override
	public boolean signoff() {
		// TODO Auto-generated method stub
		return false;
	}
	@Override
	public boolean signon() {
		// TODO Auto-generated method stub
		login(mUserName, mPassward);
//		mFetionThread.start();
		return true;
	}
	// FIXME you really do not need this when you implement a new protocol
	// this just be done bcz it's a wrapper of Fetion in AnFetion.
    public ArrayList<FetionFriend> getFetionFriend() {
    	return mBuddies;
    }
	// FIXME just for RemotePackageHandle remove this later.
	public void onRcvdIM(String who, String message) {
		/* a new instance will cause a null when find chat */
//		for (FetionFriend ff : mBuddies) {
//			if (ff.getName().equals(who)) {
//				onRcvdIM(new Message(new FriendAdapter(ff), getMyself(), message));
//			}
//		}
		List<Buddy> _list = mBuddiees.getAll();
		for (Buddy b : _list) {
			// FIXME b.getUserName() == FetionFriend.getName() so when either one 
			//change u must modify this.
			if (b.getUserName().equals(who)) {
				mChats.getOrNewConversation(b).doImRcvd(new Message(b, getMyself(), message));
			}
		}
	}

	@Override
	public boolean sendIm(Message e) {
		// TODO Auto-generated method stub
		Conversation c = mChats.findConversation(e.getTo());
		String who = e.getTo().getID();
		String message = e.getMessage();
		
		SendSMS send = new SendSMS(mSerialManager, mTcpConn, mUri, who, message);
		
		Log.d(TAG, " a SendSMS object has created");
		if (false == send.execute()) {
			Log.e(TAG, "cannot send the IM: " + message.toString());
			c.doImErr(0, "cannot send the IM: " + message.toString());
			
			return false;
		}
		c.doImSend(e);
		
		return true;
	}
	
	private class MainHandler extends Handler {
		private static final int CODE_RCVD_MSG = 1;
		private static final int CODE_LOGIN_STEP = 2;
		private static final int CODE_LOGIN_ERR	= 3;
		
		@Override
		public void handleMessage(android.os.Message msg) {
			// TODO Auto-generated method stub
			String message = null;
			switch (msg.what) {
			case CODE_RCVD_MSG:
				message = (String ) msg.obj;
				Bundle b = msg.peekData();
				String who = b.getString("who");
				onRcvdIM(who, message);
				break;
			case CODE_LOGIN_STEP:
				message = (String) msg.obj;
				int step = msg.arg1;
				int steps = msg.arg2;
				
				dispatchLoginStep(step, steps, message);
				break;
			case CODE_LOGIN_ERR:
				message = (String) msg.obj;
				int code = msg.arg1;
				
				dispatchLoginErr(code, message);
				break;
			default:
				break;
			}
			
			return;
		}
	}
	private Handler mMainHandler = new MainHandler();

	
	private  Thread mFetionThread = new Thread(new Runnable(){

		public void run() {
			// TODO Auto-generated method stub
			login(mUserName, mPassward);
		}
		
	});
}